﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;

using Neon.Common;
using Neon.IO;
using Neon.Net;
using Neon.SSH;
using Renci.SshNet.Common;

namespace RaspberryDebugger
{
    /// <summary>
    /// 实现一个SSH连接到远程树莓派。  
    /// </summary>
    internal class Connection : LinuxSshProxy
    {
        //---------------------------------------------------------------------
        // Static members

        /// <summary>
        /// 建立到远程树莓派的SSH连接  
        ///连接信息被传递。  
        /// </summary>
        /// <param name="connectionInfo">连接信息。</param>
        /// <param name="usePassword">可选地强制使用密码而不是公钥。  </param>
        /// <param name="projectSettings">
        /// 可选地指定项目设置。 必须为以下连接指定此参数  
        /// 将用于远程调试，但可能忽略仅用于设置的连接  
        ///  SSH密钥等。  
        /// </param>
        /// <returns>The connection.</returns>
        /// <exception cref="Exception">当无法建立连接时抛出。  </exception>
        public static async Task<Connection> ConnectAsync(ConnectionInfo connectionInfo, bool usePassword = false, ProjectSettings projectSettings = null)
        {
            Covenant.Requires<ArgumentNullException>(connectionInfo != null, nameof(connectionInfo));

            try
            {
                if (!NetHelper.TryParseIPv4Address(connectionInfo.Host, out var address))
                {
                    Log($"DNS lookup for: {connectionInfo.Host}");

                    address = (await Dns.GetHostAddressesAsync(connectionInfo.Host)).FirstOrDefault();
                }

                if (address == null)
                {
                    throw new ConnectionException(connectionInfo.Host, "DNS lookup failed.");
                }

                SshCredentials credentials;

                if (string.IsNullOrEmpty(connectionInfo.PrivateKeyPath) || usePassword)
                {
                    Log($"[{connectionInfo.Host}]: Auth via username/password");

                    credentials = SshCredentials.FromUserPassword(connectionInfo.User, connectionInfo.Password);
                }
                else
                {
                    Log($"[{connectionInfo.Host}]: Auth via SSH keys");

                    credentials = SshCredentials.FromPrivateKey(connectionInfo.User, File.ReadAllText(connectionInfo.PrivateKeyPath));
                }

                var connection = new Connection(connectionInfo.Host, address, connectionInfo, credentials, projectSettings);

                connection.Connect(TimeSpan.Zero);
                await connection.InitializeAsync();

                return connection;
            }
            catch (SshProxyException e)
            {
                if (usePassword ||
                    e.InnerException == null ||
                    e.InnerException.GetType() != typeof(SshAuthenticationException))
                {
                    RaspberryDebugger.Log.Exception(e, $"[{connectionInfo.Host}]");
                    throw;
                }

                if (string.IsNullOrEmpty(connectionInfo.PrivateKeyPath) ||
                    string.IsNullOrEmpty(connectionInfo.Password))
                {
                    RaspberryDebugger.Log.Exception(e, $"[{connectionInfo.Host}]:连接必须有密码或SSH私钥。  ");
                    throw;
                }

                RaspberryDebugger.Log.Warning($"[{connectionInfo.Host}]: SSH auth failed:尝试使用密码重新授权公钥  ");

                // SSH私钥认证不起作用。 这通常发生  
                //在用户重新获得树莓后。 很可能  
                // user设置了相同的用户名/密码，所以我们将尝试登录  
                //只需要配置当前的公钥即可  
                //关于Raspberry。  

                try
                {
                    var connection = await ConnectAsync(connectionInfo, usePassword: true);

                    // 如果是，将公钥追加到用户的[authorized_keys]文件  
                    //不存在。  

                    RaspberryDebugger.Log.Info($"[{connectionInfo.Host}]: Reauthorizing the public key");

                    var homeFolder = LinuxPath.Combine("/", "home", connectionInfo.User);
                    var publicKey  = File.ReadAllText(connectionInfo.PublicKeyPath).Trim();
                    var keyScript  =
$@"
mkdir -p {homeFolder}/.ssh
touch {homeFolder}/.ssh/authorized_keys

if ! grep --quiet '{publicKey}' {homeFolder}/.ssh/authorized_keys ; then
    echo '{publicKey}' >> {homeFolder}/.ssh/authorized_keys
    exit $?
fi

exit 0
";
                    connection.ThrowOnError(connection.RunCommand(CommandBundle.FromScript(keyScript)));
                    return connection;
                }
                catch (Exception e2)
                {
                    // We've done all we can.

                    RaspberryDebugger.Log.Exception(e2, $"[{connectionInfo.Host}]");
                    throw;
                }
            }
            catch (Exception e)
            {
                RaspberryDebugger.Log.Exception(e, $"[{connectionInfo.Host}]");
                throw;
            }
        }

        /// <summary>
        /// 将一行文本记录到Visual Studio调试窗格。  
        /// </summary>
        /// <param name="text">The text.</param>
        private new static void Log(string text = "")
        {
            if (string.IsNullOrEmpty(text))
            {
                RaspberryDebugger.Log.WriteLine(text);
            }
            else
            {
                RaspberryDebugger.Log.Info(text);
            }
        }

        //---------------------------------------------------------------------
        // Instance members

        private ConnectionInfo      connectionInfo;
        private ProjectSettings     projectSettings;

        /// <summary>
        /// 使用密码构造连接。
        /// </summary>
        /// <param name="name">The server name.</param>
        /// <param name="address">The IP address.</param>
        /// <param name="connectionInfo">The connection information.</param>
        /// <param name="credentials">The SSH credentials.</param>
        /// <param name="projectSettings">
        /// constructoptional指定项目设置。 必须为以下连接指定此参数  
        /// 将用于远程调试，但可能忽略仅用于设置的连接  
        /// 像SSH密钥之类的东西。  
        /// </param>
        private Connection(string name, IPAddress address, ConnectionInfo connectionInfo, SshCredentials credentials, ProjectSettings projectSettings)
            : base(name, address, credentials, connectionInfo.Port, logWriter: null)
        {
            this.connectionInfo  = connectionInfo;
            this.projectSettings = projectSettings;

            // 禁用连接级别日志记录等。

            DefaultRunOptions = RunOptions.None;
        }

        /// <summary>
        /// 返回远程树莓的相关状态信息，包括  
        ///芯片架构，<b>vsdbg</b>调试器状态，以及安装  
        /// . net核心sdk。  
        /// </summary>
        public Status PiStatus { get; private set; }

        /// <summary>
        ///日志信息显示Visual Studio调试窗格。
        /// </summary>
        /// <param name="text">The error text.</param>
        private void LogInfo(string text)
        {
            RaspberryDebugger.Log.Info($"[{Name}]: {text}");
        }

        /// <summary>
        ///将错误记录到Visual Studio调试窗格。  
        /// </summary>
        /// <param name="text">The error text.</param>
        private void LogError(string text)
        {
            RaspberryDebugger.Log.Error($"[{Name}]: {text}");
        }

        /// <summary>
        ///将警告记录到Visual Studio调试窗格。  
        /// </summary>
        /// <param name="text">The error text.</param>
        private void LogWarning(string text)
        {
            RaspberryDebugger.Log.Warning($"[{Name}]: {text}");
        }

        /// <summary>
        /// 将异常记录到Visual Studio调试窗格。  
        /// </summary>
        /// <param name="e">The exception.</param>
        private new void LogException(Exception e)
        {
            RaspberryDebugger.Log.Exception(e, $"[{Name}]");
        }

        /// <summary>
        /// Throws a <see cref="ConnectionException"/> if a remote command failed.
        /// </summary>
        /// <param name="commandResponse">The remote command response.</param>
        /// <returns>The <see cref="CommandResponse"/> on success.</returns>
        private CommandResponse ThrowOnError(CommandResponse commandResponse)
        {
            Covenant.Requires<ArgumentNullException>(commandResponse != null, nameof(commandResponse));

            if (commandResponse.ExitCode != 0)
            {
                throw new ConnectionException(this, commandResponse.ErrorText);
            }

            return commandResponse;
        }

        /// <summary>
        ///通过从远程树莓获取状态来初始化连接，并确保  
        ///表示安装了执行远程命令所需的所有包。 这也将  
        ///在工作站和远程树莓上创建并配置SSH密钥对  
        ///不存在，因此后续的连接可以使用基于密钥的身份验证。  
        /// </summary>
        /// <returns>The tracking <see cref="Task"/>.</returns>
        private async Task InitializeAsync()
        {
            await PackageHelper.ExecuteWithProgressAsync(
                $"Connecting to [{Name}]...",
                async () =>
                {
                    // 禁用此功能是因为它看起来像禁用了SUDO密码提示  
                    //默认为树莓派操作系统。  
#if DISABLED
                    // This call ensures that SUDO password prompting is disabled and the
                    // the required hidden folders exist in the user's home directory.

                    DisableSudoPrompt(password);
#endif
                    // 我们需要确保[unzip]已经安装，以便[LinuxSshProxy]命令  
                    // bundle将工作。  

                    Log($"[{Name}]: Checking for: [unzip]");

                    var response = SudoCommand("which unzip");

                    if (response.ExitCode != 0)
                    {
                        Log($"[{Name}]: Installing: [unzip]");

                        ThrowOnError(SudoCommand("sudo apt-get update"));
                        ThrowOnError(SudoCommand("sudo apt-get install -yq unzip"));
                    }

                    //为了提高速度，我们将执行一个脚本，在单个操作中收集所有内容。  

                    Log($"[{Name}]: Retrieving status");

                    var statusScript =
$@"
# This script will return the status information via STDOUT line-by-line
# in this order:
#
# Chip Architecture
# PATH environment variable
# Unzip Installed (""unzip"" or ""unzip-missing"")
# Debugger Installed (""debugger-installed"" or ""debugger-missing"")
# List of installed SDKs names (e.g. 3.1.108) separated by commas
# Raspberry Model like:     Raspberry Pi 4 Model B Rev 1.2
# Raspberry Revision like:  c03112
#
# This script also ensures that the [/lib/dotnet] directory exists, that
# it has reasonable permissions, and that the folder exists on the system
# PATH and that DOTNET_ROOT points to the folder.

# Set the SDK and debugger installation paths.

DOTNET_ROOT={PackageHelper.RemoteDotnetFolder}
DEBUGFOLDER={PackageHelper.RemoteDebuggerFolder}

# Get the chip architecture

uname -m

# Get the current PATH

echo $PATH

# Detect whether [unzip] is installed.

if which unzip &> /dev/nul ; then
    echo 'unzip'
else
    echo 'unzip-missing'
fi

# Detect whether the [vsdbg] debugger is installed.

if [ -d $DEBUGFOLDER ] ; then
    echo 'debugger-installed'
else
    echo 'debugger-missing'
fi

# List the SDK folders.  These folder names are the same as the
# corresponding SDK name.  We'll list the files on one line
# with the SDK names separated by commas.  We'll return a blank
# line if the SDK directory doesn't exist.

if [ -d $DOTNET_ROOT/sdk ] ; then
    ls -m $DOTNET_ROOT/sdk
else
    echo ''
fi

# Output the Raspberry board model.

cat /proc/cpuinfo | grep '^Model\s' | grep -o 'Raspberry.*$'

# Output the Raspberry board revision.

cat /proc/cpuinfo | grep 'Revision\s' | grep -o '[0-9a-fA-F]*$'

# Ensure that the [/lib/dotnet] folder exists, that it's on the
# PATH and that DOTNET_ROOT are defined.

mkdir -p /lib/dotnet
chown root:root /lib/dotnet
chmod 755 /lib/dotnet

# Set these for the current session:

export DOTNET_ROOT={PackageHelper.RemoteDotnetFolder}
export PATH=$PATH:$DOTNET_ROOT

# and for future sessions too:

if ! grep --quiet DOTNET_ROOT /etc/profile ; then

    echo """"                                >> /etc/profile
    echo ""#------------------------------"" >> /etc/profile
    echo ""# Raspberry Debugger:""           >> /etc/profile
    echo ""export DOTNET_ROOT=$DOTNET_ROOT"" >> /etc/profile
    echo ""export PATH=$PATH""               >> /etc/profile
    echo ""#------------------------------"" >> /etc/profile
fi
";
                    Log($"[{Name}]: Fetching status");

                    response = ThrowOnError(SudoCommand(CommandBundle.FromScript(statusScript)));

                    using (var reader = new StringReader(response.OutputText))
                    {
                        var architecture = await reader.ReadLineAsync();
                        var path         = await reader.ReadLineAsync();
                        var hasUnzip     = await reader.ReadLineAsync() == "unzip";
                        var hasDebugger  = await reader.ReadLineAsync() == "debugger-installed";
                        var sdkLine      = await reader.ReadLineAsync();
                        var model        = await reader.ReadLineAsync();
                        var revision     = await reader.ReadToEndAsync();

                        revision = revision.Trim();     //删除末尾的任何空白。

                        Log($"[{Name}]: architecture: {architecture}");
                        Log($"[{Name}]: path:         {path}");
                        Log($"[{Name}]: unzip:        {hasUnzip}");
                        Log($"[{Name}]: debugger:     {hasDebugger}");
                        Log($"[{Name}]: sdks:         {sdkLine}");
                        Log($"[{Name}]: model:        {model}");
                        Log($"[{Name}]: revision:     {revision}");

                        //将逗号分隔的SDK名称转换为[PiSdk]列表。  

                        var sdks = new List<Sdk>();

                        foreach (var sdkName in sdkLine.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(sdk => sdk.Trim()))
                        {
                            // $todo(jefflill):我们现在只支持32位的sdk。  

                            var sdkCatalogItem = PackageHelper.SdkCatalog.Items.SingleOrDefault(item => item.Name == sdkName && item.Architecture == SdkArchitecture.ARM32);

                            if (sdkCatalogItem != null)
                            {
                                sdks.Add(new Sdk(sdkName, sdkCatalogItem.Version));
                            }
                            else
                            {
                                LogWarning($". net SDK [{sdkName}]存在于[{Name}]上，但是RaspberryDebugger扩展不知道。 考虑更新扩展。  ");
                            }
                        }

                        PiStatus = new Status(
                            architecture:  architecture,
                            path:          path,
                            hasUnzip:      hasUnzip,
                            hasDebugger:   hasDebugger,
                            installedSdks: sdks,
                            model:         model,
                            revision:      revision
                        );
                    }
                });

            // 如果不存在SSH密钥，则为该连接创建并配置SSH密钥。  

            if (string.IsNullOrEmpty(connectionInfo.PrivateKeyPath) || !File.Exists(connectionInfo.PrivateKeyPath))
            {
                await PackageHelper.ExecuteWithProgressAsync("Creating SSH keys...",
                    async () =>
                    {
                        //在树莓上创建一个2048位的没有密码的私钥  
                        //然后下载到我们的keys文件夹。 密钥文件名将  
                        //是树莓的主机名。  
                        LogInfo("Creating SSH keys");

                        var workstationUser    = Environment.GetEnvironmentVariable("USERNAME");
                        var workstationName    = Environment.GetEnvironmentVariable("COMPUTERNAME");
                        var keyName            = Guid.NewGuid().ToString("d");
                        var homeFolder         = LinuxPath.Combine("/", "home", connectionInfo.User);
                        var tempPrivateKeyPath = LinuxPath.Combine(homeFolder, keyName);
                        var tempPublicKeyPath  = LinuxPath.Combine(homeFolder, $"{keyName}.pub");

                        try
                        {
                            var createKeyScript =
$@"
# Create the key pair

if ! ssh-keygen -t rsa -b 2048 -P '' -C '{workstationUser}@{workstationName}' -f {tempPrivateKeyPath} -m pem ; then
    exit 1
fi

# Append the public key to the user's [authorized_keys] file to enable it.

mkdir -p {homeFolder}/.ssh
touch {homeFolder}/.ssh/authorized_keys
cat {tempPublicKeyPath} >> {homeFolder}/.ssh/authorized_keys

exit 0
";
                            ThrowOnError(RunCommand(CommandBundle.FromScript(createKeyScript)));

                            // 下载公钥和私钥，并将它们持久化到工作站  
                            //然后更新连接信息。  

                            var connections            = PackageHelper.ReadConnections();
                            var existingConnectionInfo = connections.SingleOrDefault(c => c.Name == connectionInfo.Name);
                            var publicKeyPath          = Path.Combine(PackageHelper.KeysFolder, $"{connectionInfo.Name}.pub");
                            var privateKeyPath         = Path.Combine(PackageHelper.KeysFolder, connectionInfo.Name);

                            File.WriteAllBytes(publicKeyPath, DownloadBytes(tempPublicKeyPath));
                            File.WriteAllBytes(privateKeyPath, DownloadBytes(tempPrivateKeyPath));

                            connectionInfo.PrivateKeyPath = privateKeyPath;
                            connectionInfo.PublicKeyPath  = publicKeyPath;

                            if (existingConnectionInfo != null)
                            {
                                existingConnectionInfo.PrivateKeyPath = privateKeyPath;
                                existingConnectionInfo.PublicKeyPath  = publicKeyPath;

                                PackageHelper.WriteConnections(connections, disableLogging: true);
                            }
                        }
                        finally
                        {
                            //删除树莓上的临时密钥文件。  

                            var removeKeyScript =
$@"
rm -f {tempPrivateKeyPath}
rm -f {tempPublicKeyPath}
";
                            ThrowOnError(SudoCommand(CommandBundle.FromScript(removeKeyScript)));
                        }

                        await Task.CompletedTask;
                    });
            }
        }

        /// <summary>
        ///安装指定的.net Core SDK到Raspberry Pi，如果它还没有安装。  
        /// </summary>
        /// <param name="sdkVersion">The SDK version.</param>
        /// <returns><c>true</c> on success.</returns>
        public async Task<bool> InstallSdkAsync(string sdkVersion)
        {
            Covenant.Requires<ArgumentNullException>(!string.IsNullOrEmpty(sdkVersion), nameof(sdkVersion));

            // $todo(jefflill):
            //
            // 注意，我们将为SDK安装独立的SDK  
            //而不是Visual Studio自带的SDK。 我
            //假设Visual Studio sdk可能有额外的东西，我们没有  
            //需要，它也可能是Visual Studio SDK的SDK  
            //版本可能还没有发布。  
            //  
            //我们将来可能要重新评估这个问题。  

            if (PiStatus.InstalledSdks.Any(sdk => sdk.Version == sdkVersion))
            {
                return await Task.FromResult(true);    // Already installed
            }

            LogInfo($". net Core SDK [v{sdkVersion}]未安装。  ");

            // 找到请求.net版本的独立SDK。  

            var targetSdk = PackageHelper.SdkGoodCatalog.Items.SingleOrDefault(item => item.IsStandalone && item.Version == sdkVersion && item.Architecture == SdkArchitecture.ARM32);

            if (targetSdk == null)
            {
                //回到Visual Studio SDK，如果有的话。  

                targetSdk = PackageHelper.SdkGoodCatalog.Items.SingleOrDefault(item => item.Version == sdkVersion);
                LogInfo($"无法找到独立的SDK [{sdkVersion}]为回落到[{targetSdk.Name}],版本[v {targetSdk.Version}]。  ");
            }

            if (targetSdk == null)
            {
                LogError($"RasberryDebug不知道.net Core SDK [v{sdkVersion}]。  ");
                LogError($"尝试更新RasberryDebug扩展或报告这个问题:  ");
                LogError($"https://github.com/nforgeio/RaspberryDebugger/issues");

                return await Task.FromResult(false);
            }

            // 安装SDK.

            LogInfo($"安装SDK v{targetSdk.Version}");

            return await PackageHelper.ExecuteWithProgressAsync($"下载并安装。net v{targetSdk的SDK。 版本}Raspberry…  ",
                async () =>
                {
                    var installScript =
$@"
export DOTNET_ROOT={PackageHelper.RemoteDotnetFolder}

# Ensure that the packages required by .NET Core are installed:
#
#       https://docs.microsoft.com/en-us/dotnet/core/install/linux-debian#dependencies

if ! apt-get update ; then
    exit 1
fi

if ! apt-get install -yq libc6 libgcc1 libgssapi-krb5-2 libicu-dev libssl1.1 libstdc++6 zlib1g libgdiplus ; then
    exit 1
fi

# Remove any existing SDK download.  This might be present if a
# previous installation attempt failed.

if ! rm -f /tmp/dotnet-sdk.tar.gz ; then
    exit 1
fi

# Download the SDK installation file to a temporary file.

if ! wget --quiet -O /tmp/dotnet-sdk.tar.gz {targetSdk.Link} ; then
    exit 1
fi

# Verify the SHA512.

orgDir=$cwd
cd /tmp

if ! echo '{targetSdk.SHA512}  dotnet-sdk.tar.gz' | sha512sum --check - ; then
    cd $orgDir
    exit 1
fi

cd $orgDir

# Make sure the installation directory exists.

if ! mkdir -p $DOTNET_ROOT ; then
    exit 1
fi

# Unpack the SDK to the installation directory.

if ! tar -zxf /tmp/dotnet-sdk.tar.gz -C $DOTNET_ROOT --no-same-owner ; then
    exit 1
fi

# Remove the temporary installation file.

if ! rm /tmp/dotnet-sdk.tar.gz ; then
    exit 1
fi

exit 0
";
                    try
                    {
                        var response = SudoCommand(CommandBundle.FromScript(installScript));

                        if (response.ExitCode == 0)
                        {
                            // Add the newly installed SDK to the list of installed SDKs.

                            PiStatus.InstalledSdks.Add(new Sdk(targetSdk.Name, targetSdk.Version));
                            return await Task.FromResult(true);
                        }
                        else
                        {
                            LogError(response.AllText);
                            return await Task.FromResult(false);
                        }
                    }
                    catch (Exception e)
                    {
                        LogException(e);
                        return await Task.FromResult(false);
                    }
                });
        }

        /// <summary>
        /// Installs the <b>vsdbg</b> debugger on the Raspberry if it's not already installed.
        /// </summary>
        /// <returns><c>true</c> on success.</returns>
        public async Task<bool> InstallDebuggerAsync()
        {
            if (PiStatus.HasDebugger)
            {
                return await Task.FromResult(true);
            }

            LogInfo($"Installing VSDBG to: [{PackageHelper.RemoteDebuggerFolder}]");

            return await PackageHelper.ExecuteWithProgressAsync<bool>($"Installing [vsdbg] debugger...",
                async () =>
                {
                    var installScript =
$@"
if ! curl -sSL https://aka.ms/getvsdbgsh | /bin/sh /dev/stdin -v latest -l {PackageHelper.RemoteDebuggerFolder} ; then
    exit 1
fi

exit 0
";
                    try
                    {
                        var response = SudoCommand(CommandBundle.FromScript(installScript));

                        if (response.ExitCode == 0)
                        {
                            // Indicate that debugger is now installed.

                            PiStatus.HasDebugger = true;
                            return await Task.FromResult(true);
                        }
                        else
                        {
                            LogError(response.AllText);
                            return await Task.FromResult(false);
                        }
                    }
                    catch (Exception e)
                    {
                        LogException(e);
                        return await Task.FromResult(false);
                    }
                });
        }

        /// <summary>
        /// Uploads the files for the program being debugged to the Raspberry, replacing
        /// any existing files.
        /// </summary>
        /// <param name="programName">The program name</param>
        /// <param name="assemblyName">The addembly name.</param>
        /// <param name="publishedBinaryFolder">Path to the workstation folder holding the program files.</param>
        /// <returns><c>true</c> on success.</returns>
        public async Task<bool> UploadProgramAsync(string programName, string assemblyName, string publishedBinaryFolder)
        {
            Covenant.Requires<ArgumentNullException>(!string.IsNullOrEmpty(programName), nameof(programName));
            Covenant.Requires<ArgumentException>(!programName.Contains(' '), nameof(programName));
            Covenant.Requires<ArgumentNullException>(!string.IsNullOrEmpty(assemblyName), nameof(assemblyName));
            Covenant.Requires<ArgumentException>(!assemblyName.Contains(' '), nameof(assemblyName));
            Covenant.Requires<ArgumentNullException>(!string.IsNullOrEmpty(publishedBinaryFolder), nameof(publishedBinaryFolder));
            Covenant.Requires<ArgumentNullException>(Directory.Exists(publishedBinaryFolder), nameof(publishedBinaryFolder));

            // We're going to ZIP the program files locally and then transfer the zipped
            // files to the Raspberry to be expanded there.

            var debugFolder = LinuxPath.Combine(PackageHelper.RemoteDebugBinaryRoot(Username), programName);
            var groupScript = string.Empty;

            if (!string.IsNullOrEmpty(projectSettings.TargetGroup))
            {
                groupScript =
$@"
# Add the program assembly to the user specified target group (if any).  This
# defaults to [gpio] so users will be able to access the GPIO pins.

if ! chgrp {projectSettings.TargetGroup} {debugFolder}/{assemblyName} ; then
    exit 1
fi
";
            }

            var uploadScript =
$@"

# Ensure that the debug folder exists.

if ! mkdir -p {debugFolder} ; then
    exit 1
fi

# Clear all existing program files.

if ! rm -rf {debugFolder}/* ; then
    exit 1
fi

# Unzip the binary and other files to the debug folder.

if ! unzip program.zip -d {debugFolder} ; then
    exit 1
fi

# The program assembly needs execute permissions.

if ! chmod 770 {debugFolder}/{assemblyName} ; then
    exit 1
fi
{groupScript}
exit 0
";
            // I'm not going to do a progress dialog because this should be fast.

            try
            {
                LogInfo($"Uploading program to: [{debugFolder}]");

                var bundle = new CommandBundle(uploadScript);

                bundle.AddZip("program.zip", publishedBinaryFolder);

                var response = RunCommand(bundle);

                if (response.ExitCode == 0)
                {
                    LogInfo($"Program uploaded");
                    return await Task.FromResult(true);
                }
                else
                {
                    LogError(response.AllText);
                    return await Task.FromResult(false);
                }
            }
            catch (Exception e)
            {
                LogException(e);
                return await Task.FromResult(false);
            }
        }
    }
}
